import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { FindOptionsWhere, Like } from 'typeorm';
import {DbService} from "@/db/db.service";
import {ReqProvider} from "@/core/providers/req.provider";
import IResponse from "@/services/response.service";
import {concatMap, of, map, defer, delay, from} from "rxjs";
import {AccountEntity} from "@/db/entitys/account.entity";
import { BiliBindService } from '@/modules/bili/bili.bind.service';
import { DyBindService } from '@/modules/dy/dy.bind.service';
import { DyService } from '@/modules/dy/dy.service';
import ILogger from '@/services/logger.service';
import { IWonewSer } from '@/typing';
import { ConfigService } from '@nestjs/config';
import { KsBindService } from '../ks/ks.bind.service';
import { Http } from 'winston/lib/winston/transports';
import IHelper from '@/services/helper.service';
import axios from 'axios';
import * as moment from 'moment';
import { BiliUserActionHookService } from '@/modules/bili/user-action-hook/bili.user.action.hook.service';
import { DyUserActionHookService } from '../dy/dy.user.action.hook.service';
import * as puppeteer from "puppeteer"
import IGlobal from '@/services/global.service';
import { BiliOtherService } from '../bili/bili.other.service';

@Injectable()
export class AccountService {
  constructor(
    private readonly dbService: DbService,
    private readonly configService: ConfigService,
    private readonly reqProvider: ReqProvider,
    private readonly biliBindService: BiliBindService,
    private readonly biliOtherService: BiliOtherService,
    private readonly dyBindService: DyBindService,
    private readonly ksBindService: KsBindService,
    private readonly dyService: DyService,
    private readonly biliUserActionHookService: BiliUserActionHookService,
    private readonly dyUserActionHookService: DyUserActionHookService,
  ) {
  }
  getList(opt: IWonewSer.QueryFilterParams) {
    const user = this.reqProvider.user
    const page = opt?.page || 1
    const pageSize = opt.pageSize || this.configService.get("PAGE_SIZE", 30)
    const skip = (page - 1) * pageSize
    const whereOptions: FindOptionsWhere<AccountEntity> = {
      uid: user.id
    }
    if (opt.keyword) {
      whereOptions["name"] = Like(`%${opt.keyword}%`)
    }
    return defer(() =>
      this.dbService.accountRepository.find({where: whereOptions, skip, take: pageSize})
    ).pipe(
      concatMap(list => 
        this.dbService.accountRepository.count().then(total => {
          return {
            list, total, pageSize, page,
          }
        })
      ),
      map(res => IResponse.success(res))
    )
  }
  createOrEdit(id: number, data: {name: string, type: number}) {
    const user = this.reqProvider.user
    return of(null).pipe(
      concatMap(() => {
        if (id) {
          return this.dbService.accountRepository.findOneByOrFail({id, uid: user.id})
        }
        return of(new AccountEntity())
      }),
      concatMap(newAcc => {
        newAcc.uid = user.id
        newAcc.type = data.type
        newAcc.name = data.name
        return this.dbService.accountRepository.save(newAcc, {reload: true})
      }),
      map(newAcc => IResponse.success(newAcc))
    )
  }

  delete(id: number) {
    const user = this.reqProvider.user
    return defer(() =>
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id})
    ).pipe(
      concatMap(ent => {
        return this.dbService.accountRepository.remove(ent)
      }),
      delay(1000),
      map(() => IResponse.success(null))
    )
  }

  bindPrepare(id: number) {
    const user = this.reqProvider.user
    return defer(() => 
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id})
    ).pipe(
      concatMap((ent) => {
        if (ent.type === 1) {
          return this.biliBindService.prepare(id)
        }
        if (ent.type === 2) {
          return this.dyBindService.prepare(id)
        }
        if (ent.type === 3) {
          return this.ksBindService.prepare(id)
        }
        throw new HttpException("Account type not valid", HttpStatus.BAD_REQUEST)
      }),
      map(task => IResponse.success({hash: task.hash, state: task.state, targetUri: task.targetUri}))
    )
  }

  bindCheck(id: number, opt: {hash: string}) {
    const user = this.reqProvider.user
    return defer(() => 
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id})
    ).pipe(
      concatMap((ent) => {
        if (ent.type === 1) {
          return this.biliBindService.check(opt.hash).pipe(
            map(task => ({task, ent}))
          )
        }
        if (ent.type === 2) {
          return this.dyBindService.check(opt.hash).pipe(
            map(task => ({task, ent}))
          )
        }
        if (ent.type === 3) {
          return this.ksBindService.check(opt.hash).pipe(
            map(task => ({task, ent}))
          )
        }
        throw new HttpException("Account type not valid", HttpStatus.BAD_REQUEST)
      }),
      concatMap(({task, ent}) => {
        if (task.state === 3) {
          ent.cookie = task.cookie
          ent.platform_user = JSON.stringify(task.platformUser)
          ent.status = 1
          return this.dbService.accountRepository.save(ent, {reload: true}).then(() => task)
        }
        return of(task)
      }),
      map(task => IResponse.success({hash: task.hash, state: task.state, targetUri: task.targetUri}))
    )
  }

  async closeHomePage(hashId: string) {
    if (!IGlobal.store.puppeteerBrowserCache) {
      IGlobal.store.puppeteerBrowserCache = {}
    }
    const browser: puppeteer.Browser | undefined = IGlobal.store.puppeteerBrowserCache[hashId]
    if (browser) {
      await browser.close()
      delete IGlobal.store.puppeteerBrowserCache[hashId]
    }
    return IResponse.success(null)
  }
  async openHomePage(id: number) {
    const account = await this.dbService.accountRepository.findOneByOrFail({id})
    let browser: puppeteer.Browser | undefined = undefined
    browser = await puppeteer.launch({
      headless: false,
      defaultViewport: {
        width: 1280, height: 720
      },
      args: ['--no-sandbox','--disable-setuid-sandbox', "--window-size=1280,720"],
    })
    const page = await browser.newPage()
    const cookies = JSON.parse(account.cookie) as puppeteer.Cookie[]
    for(let i = 0; i < cookies.length; i++){
      await page.setCookie(cookies[i])
    }
    await page.goto("https://member.bilibili.com/platform/home")
    const hashId = IHelper.hash(32)
    if (!IGlobal.store.puppeteerBrowserCache) {
      IGlobal.store.puppeteerBrowserCache = {}
    }
    IGlobal.store.puppeteerBrowserCache[hashId] = browser
    return IResponse.success(hashId)
    
  }

  checkCookie(opt: {account?: AccountEntity, id?: number}) {
    return of(null).pipe(
      concatMap(() => {
        return opt.account ? of(opt.account) : this.dbService.accountRepository.findOneByOrFail({id: opt.id})
      }),
      concatMap(account => {
        if (account.type === 1) {
          return this.biliBindService.checkCookie(account.cookie).pipe(
            map(payload => ({account, ...payload}))
          )
        }
        if (account.type === 2) {
          return this.dyBindService.checkCookie(account.cookie).pipe(
            map(payload => ({account, ...payload}))
          )
        }
        if (account.type === 3) {
          return this.ksBindService.checkCookie(account.cookie).pipe(
            map(payload => ({account, ...payload}))
          )
        }
        throw new HttpException("Account type not valid", HttpStatus.BAD_REQUEST)
      }),
      concatMap(({account, valid, platformUser, cookie}) => {
        console.log("platformUser", typeof platformUser)
        ILogger.info(`Check account cookie: ${account.name} cookie status is ${valid}`)
        if (!valid) {
          account.cookie = ""
          account.status = 2
          account.platform_user = null
        } else {
          account.status = 1
          if (cookie) {
            account.cookie = cookie
          }
          if (platformUser) {
            account.platform_user = JSON.stringify(platformUser)
          }
        }
        return this.dbService.accountRepository.save(account, {reload: true})
      })
    )
  }

  getPosts(id: number, opt: IWonewSer.PaginationPrams & {max_cursor?: string}) {
    const user = this.reqProvider.user
    return from(
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id})
    ).pipe(
      concatMap(entity => {
        if (entity.type === 1) {
          return this.biliOtherService.getPosts(entity.cookie, opt).pipe(
            map(d => ({...d, account: entity}))
          )
        }
        if (entity.type === 2) {
          return this.dyService.getPosts(entity.cookie, opt).pipe(
            map(d => ({...d, account: entity}))
          )
        }
      }),
      map(d => IResponse.success(d))
    )
  }

  getTypeVars() {
    const typeVars = [
      {type: 1, name: "BiliBili"},
      {type: 2, name: "抖音"},
      {type: 3, name: "快手"},
    ]
    return IResponse.success(typeVars)
  }

  getCookie(id: number) {
    return defer(() =>
      this.dbService.accountRepository.findOneByOrFail({id})
    ).pipe(
      map(ent => {
        return JSON.parse(ent.cookie).map(e => ({name: e.name, value: e.value}))
      })
    )
  }

  dySmsSend(id: number) {
    const user = this.reqProvider.user
    return from(
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id, type: 2})
    ).pipe(
      concatMap(entity => {
        return this.dyService.sendSmsCode(IHelper.covert2StandardCookieStr(entity.cookie))
      })
    )
  }
  dySmsValidate(id: number, code: string) {
    const user = this.reqProvider.user
    return from(
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id, type: 2})
    ).pipe(
      concatMap(entity => {
        return this.dyService.validateSmsCode(IHelper.covert2StandardCookieStr(entity.cookie), code)
      })
    )
  }

  shareCookie(id: number, cookie: string) {
    const user = this.reqProvider.user
    return from(
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id, type: 2})
    ).pipe(
      concatMap(entity => {
        entity.cookie = cookie
        return this.dbService.accountRepository.save(entity, {reload: true})
      })
    )
  }

  updateHookUserAction(id: number) {
    const user = this.reqProvider.user
    return from(
      this.dbService.accountRepository.findOneByOrFail({id, uid: user.id})
    ).pipe(
      concatMap(entity => {
        const nextState = entity.hook_user_action === 1 ? 0 : 1
        entity.hook_user_action = nextState
        return this.dbService.accountRepository.save(entity, {reload: true})
      }),
      map(d => IResponse.success(d))
    )
  }
  fireHookUserAction(id: number) {
    return from(
      this.dbService.accountRepository.findOneByOrFail({id})
    ).pipe(
      map(entity => {
        if (entity.hook_user_action !== 1) {
          throw new HttpException("Hook user action not set", HttpStatus.BAD_REQUEST)
        }
        if (entity.type === 1) {
          return this.biliUserActionHookService.fire(entity.id, `[${entity.name}-Bilibili-${entity.id}-养号]`)
        }
        if (entity.type === 2) {
          return this.dyUserActionHookService.fire(entity.id, `[${entity.name}-抖音-${entity.id}-养号]`)
        }
        return {status: true}
      })
    )
  }
}